/*
 *  Copyright (C) 2012 Компания ВИТ. web: http://diasled.ru Е-mail:  vit@diasled.ru
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package ru.apertum.diasled.model;

import java.io.Serializable;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.annotations.Sort;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Restrictions;
import ru.apertum.diasled.common.Uses;
import ru.apertum.diasled.common.Uses.ClientException;
import ru.apertum.diasled.db.HibernateUtil;

/**
 * Карточка пациента:
 * ФИО
 * Дата рождения
 * Пол
 * Вес
 * Адрес проживания
 * Контактный телефон
 * Профессия
 * Направление на обследование (источник)
 * Симптомы
 * Диагноз (заполняется по результатам обследований)
 * Назначения врача
 * 
 * @author Evgeniy Egorov, Aperum Projects
 */
@Entity
@Table(name = "patient")
@javax.persistence.TableGenerator(name = "PATIENT_GEN_ID",
table = "TABLE_GEN_ID",
allocationSize = 1)
public class Patient implements Serializable {

    @Id
    @Column(name = "id")
    //@GeneratedValue(strategy = GenerationType.AUTO)
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "PATIENT_GEN_ID")
    private Long id;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
    /*
     * docs.jboss.org/hibernate/annotations/3.5/reference/en/html/entity.html#entity-mapping-association
     */
    /*
    Это вариает OneToMany через вспомогательную таблицу, без форкнкея в подчиненной табл. Без обратной зависимости
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name = "patient_visit", joinColumns = {
    @JoinColumn(name = "patient_id")}, inverseJoinColumns = {
    @JoinColumn(name = "visit_id")})
     * 
     */
    /* С форенкеем в зависимой, с обратной связью. mappedBy="patient" имя переменной, которая завязвнв на мастер-таблицу в зависимом классе
    @OneToMany(mappedBy="patient", fetch = FetchType.EAGER)
     * 
     */
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "patient_id")
    @OrderBy(value = "date desc")
    private Set<Visit> visits = new HashSet<>();

    public Set<Visit> getVisits() {
        return visits;
    }

    public void setVisits(Set<Visit> visits) {
        this.visits = visits;
    }
    /**
     * Имя пациента
     */
    @Column(name = "first_name", nullable = false, length = 100)
    private String firstName = "";

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    /**
     * Фамилия пациента
     */
    @Column(name = "second_name", nullable = false, length = 100)
    @Sort()
    private String secondName = "";

    public String getSecondName() {
        return secondName;
    }

    public void setSecondName(String secondName) {
        this.secondName = secondName;
    }
    /**
     * Отчество пациента
     */
    @Column(name = "middle_name", nullable = false, length = 100)
    private String middleName = "";

    public String getMiddleName() {
        return middleName;
    }

    public void setMiddleName(String middleName) {
        this.middleName = middleName;
    }
    /**
     * Дата последнего посещения
     */
    @Column(name = "last_visit", nullable = false)
    @Temporal(TemporalType.DATE)
    private Date lastVisit = new Date();

    public Date getLastVisit() {
        return lastVisit;
    }

    public void setLastVisit(Date lastVisit) {
        this.lastVisit = lastVisit;
    }
    /**
     * дата рождения
     */
    @Column(name = "birthday", nullable = false)
    @Temporal(TemporalType.DATE)
    private Date birthday = new Date();

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    /**
     * адрес
     */
    @Column(name = "address", nullable = false, length = 200)
    private String address = "";

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
    /**
     * номер телефона
     */
    @Column(name = "phone", nullable = false, length = 20)
    private String phone = "";

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
    /**
     * профессия
     */
    @Column(name = "profession", nullable = false, length = 200)
    private String profession = "";

    public String getProfession() {
        return profession;
    }

    public void setProfession(String profession) {
        this.profession = profession;
    }
    /**
     * вес
     */
    @Column(name = "weight", nullable = false)
    private Integer weight = 0;

    public Integer getWeight() {
        return weight;
    }

    public void setWeight(Integer weight) {
        this.weight = weight;
    }
    /**
     * рост
     */
    @Column(name = "height", nullable = false)
    private Integer height = 0;

    public Integer getHeight() {
        return height;
    }

    public void setHeight(Integer height) {
        this.height = height;
    }
    /**
     * Пол пациента. true - мужской
     */
    @Column(name = "sex", nullable = false)
    private Boolean sex = true;

    public Boolean getSex() {
        return sex;
    }

    public void setSex(Boolean sex) {
        this.sex = sex;
    }
    @Column(name = "comments", nullable = false, length = 2000)
    private String comments = "";

    public String getComments() {
        return comments;
    }

    public void setComments(String comments) {
        this.comments = comments;
    }

    //***********************************************************************************************************************************************************************
    //********* DAO
    //***********************************************************************************************************************************************************************
    synchronized static public void savePatient(Patient patient) throws ClientException {
        final Session sess = HibernateUtil.getInstance().getSession();
        sess.beginTransaction();
        try {
            sess.saveOrUpdate(patient);
            sess.getTransaction().commit();
        } catch (Exception ex) {
            Uses.log.logger.error("Ошибка сохранения пациента.", ex);
            sess.getTransaction().rollback();
            throw new Uses.ClientException("Ошибка сохранения пациента.", ex);
        } finally {
            sess.close();
        }
    }

    synchronized static public void removePatient(Patient patient) {
        final Session sess = HibernateUtil.getInstance().getSession();
        sess.beginTransaction();
        try {
            sess.delete(patient);
            sess.getTransaction().commit();
        } catch (Exception ex) {
            Uses.log.logger.error("Ошибка удаления пациента.", ex);
            sess.getTransaction().rollback();
        } finally {
            sess.close();
        }
    }

    /**
     * Criteria sc = session.createCriteria(clazz);
     * ...
     * // fix join return duplicates
     * sc.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
     * return sc.list();
     *     
     *     
     *     Подразумевается что hashCode() и equals() вы у объектов уже поправили
     * @param filter
     * @return 
     */
    synchronized static public LinkedList<Patient> getPatients(HashMap<String, Object> filter) {
        final Session sess = HibernateUtil.getInstance().getSession();
        try {
            if (filter == null) {
                final LinkedList li = new LinkedList(sess.createCriteria(Patient.class).setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY).list());
                if (li.isEmpty()) {
                    sess.createCriteria(Sensor.class);
                    createIndexes();
                }
                return li;
                //  return new LinkedList(sess.createCriteria(Patient.class).setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY).list());
                //  return new LinkedList(sess.createCriteria(Patient.class).list());
            } else {
                final Criteria criteria = sess.createCriteria(Patient.class).setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
                //final Criteria criteria = sess.createCriteria(Patient.class);
                for (String key : filter.keySet()) {
                    criteria.add(Restrictions.eq(key, filter.get(key)));
                }
                //criteria.addOrder(Order.asc("second_name"));
                return new LinkedList<>(criteria.list());
            }
        } finally {
            sess.close();
        }
    }

    private static void createIndexes() {
        // jdbc Connection
        try {
            //Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
            //Class.forName("com.mysql.jdbc.Driver").newInstance();
            Class.forName("org.h2.Driver").newInstance();

            try (Statement stmt = DriverManager.getConnection(Uses.DB_URL, "diasled", "diasled").createStatement()) {
                stmt.execute("CREATE INDEX last_visit_idx ON patient (last_visit)");
                stmt.execute("CREATE INDEX birthday_idx ON patient (birthday)");
                stmt.execute("CREATE INDEX second_name_idx ON patient (second_name)");

                final StringBuffer sb = new StringBuffer();
                for (int i = 0; i < 256; i++) {
                    sb.append(",s").append(i).append(" SMALLINT");
                }
                stmt.execute("create table sensor (exam_id bigint, frame_id int " + sb.toString()
                        + ", sum_0 int, sum_1 int, sum_2 int, sum_3 int, sum_4 int, sum_5 int, sum_6 int, sum_7 int, sum_8 int, sum_9 int, sum_10 int, sum_11 int"
                        + ", b_x int, b_y int, b_z int" + ")");
                sb.setLength(0);
                stmt.execute("CREATE INDEX examid_idx ON sensor (exam_id)");
                //stmt.execute("CREATE INDEX diasled.frameid_idx ON diasled.sensor (frame_id)");
                //stmt.execute("CREATE INDEX diasled.sensorid_idx ON diasled.sensor (sensor_id)");
            }
        } catch (SQLException except) {
            System.out.println(except.getMessage());
            return;
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException except) {
            Uses.log.logger.error("Indexes!", except);
        }
        Uses.log.logger.trace("Созданы индексы и таблица данных для сканирования.");
    }

    public String getName() {
        return getSecondName() + " " + getFirstName() + " " + getMiddleName();
    }

    //***********************************************************************************************************************************************************************
    //********* SERVICES
    //***********************************************************************************************************************************************************************
    public String getTextInfo() {
        return "<span style='font-size:20.0pt;color:red'>"
                + "&nbsp;№" + getId()
                + "<br>&nbsp;"
                + getName()
                + "<br>&nbsp;"
                + "последний визит "
                + Uses.format_dd_MM_yyyy.format(getLastVisit())
                + "<br>&nbsp;"
                + "</span> <span style='font-size:15.0pt;color:black'> д.р. "
                + Uses.format_dd_MM_yyyy.format(getBirthday())
                + ". полных лет "
                + getAgeOnDate(new Date())
                + "<br> <br>&nbsp;адрес: "
                + getAddress()
                + "<br>&nbsp;тел. "
                + getPhone()
                + "<br>&nbsp;профессия: "
                + getProfession()
                + "<br>&nbsp;вес "
                + getWeight()
                + "<br>&nbsp;рост "
                + getHeight()
                + "<br>&nbsp;пол "
                + (getSex() ? "мужской" : "женский")
                + "</span>";
    }

    public int getAgeOnDate(Date date) {
        final GregorianCalendar gc = new GregorianCalendar();
        gc.setTime(getBirthday());
        final GregorianCalendar gc1 = new GregorianCalendar();
        gc1.setTime(date);
        return ((gc1.get(GregorianCalendar.YEAR) - gc.get(GregorianCalendar.YEAR)) + ((gc1.get(GregorianCalendar.DAY_OF_YEAR) - gc.get(GregorianCalendar.DAY_OF_YEAR)) >= 0 ? 0 : -1));
    }

    @Override
    public String toString() {
        return getName() + " " + Uses.format_dd_MM_yyyy.format(getBirthday());
    }
}
